﻿/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;

namespace NTasking.Internals
{

    /// <summary>
    /// The <see cref="CallList"/> class represents a list of entry calls ordered according to the defined <see cref="QueueingPolicy"/>
    /// </summary>
    internal class CallList : List<MethodCall>
    {

        /// <summary>
        /// An object used to prevent concurrent access to this instance
        /// </summary>
        private readonly object _lock = new object();


        /// <summary>
        /// Add a new entry call to the current instance, according to the defined <see cref="QueueingPolicy"/> 
        /// </summary>
        /// <param name="item">The entry call to be added</param>
        internal new void Add(MethodCall item)
        {
            lock (_lock)
            {
                if (Settings.QueueingPolicy == QueueingPolicy.FifoQueueing)
                    base.Add(item);
                else
                    PriorityAppend(Count, item);
            }
        }


        /// <summary>
        /// Removes an entry call from the current instance.
        /// </summary>
        /// <param name="item">The entry call to remove.</param>
        internal new void Remove(MethodCall item)
        {
            lock (_lock)
            {
                base.Remove(item);
            }
        }


        /// <summary>
        /// Count the number of entries which match a specific condition.
        /// </summary>
        /// <param name="predicate">The condition to be matched.</param>
        /// <returns>The number of entry calls which match the condition</returns>
        internal int EntryCount(Func<MethodCall, bool> predicate)
        {
            lock (_lock)
            {
                return this.Count(predicate);
            }
        }


        /// <summary>
        /// Signal that the priority of an entry call which has already been queued in the current instance
        /// has been raised
        /// The entry call will be requeued at the tail of the list of all entry calls which have the same
        /// priority.
        /// </summary>
        /// <param name="item">The entry call which has changed priority</param>
        internal void PriorityRaised(MethodCall item)
        {
            lock (_lock)
            {
                if (Settings.QueueingPolicy != QueueingPolicy.PriorityQueueing || !Contains(item)) return;

                int current = IndexOf(item);
                base.Remove(item);
                PriorityAppend(current, item);
            }
        }


        /// <summary>
        /// Signal that the priority of an entry call which has already been queued in the current instance
        /// has been lowered.
        /// The entry call will be requeued at the head of the list of all entry calls which have the same
        /// priority.
        /// </summary>
        /// <param name="item">The entry call which has changed priority</param>
        internal void PriorityLowered(MethodCall item)
        {
            lock (_lock)
            {
                if (Settings.QueueingPolicy != QueueingPolicy.PriorityQueueing || !Contains(item)) return;

                int current = IndexOf(item);
                base.Remove(item);
                PriorityInsert(current, item);
            }
        }


        /// <summary>
        /// Insert an entry call in the list at the head of the list of entry calls which have the same priority.
        /// </summary>
        /// <param name="after">From where in the list the backward search for an insertion point will start</param>
        /// <param name="item">The entry call to be inserted</param>
        private void PriorityInsert(int after, MethodCall item)
        {
            ThreadPriority priority = item.Thread.CurrentPriority;

            // For optimisation purpose, the search for an insert position starts where
            // the entry call was previously placed
            for (int i = after; i < Count; i++)
            {
                if (this[i].Thread.CurrentPriority >= priority) continue;

                Insert(i, item);
                return;
            }

            // No entry call has the same or lower priority, insert at the end of the list
            base.Add(item);
        }


        /// <summary>
        /// Inserts an entry call in the list at the tail of the list of entry calls which have the same priority.
        /// </summary>
        /// <param name="before">From where in the list the forward search for an insertion point will start</param>
        /// <param name="item">The entry call to be inserted</param>
        private void PriorityAppend(int before, MethodCall item)
        {
            ThreadPriority priority = item.Thread.CurrentPriority;

            // For optimisation purpose, the search for an insert position starts just before where
            // the entry call was previously placed
            for (int i = before - 1; i >= 0; i--)
            {
                if (this[i].Thread.CurrentPriority < priority) continue;

                Insert(i + 1, item);
                return;
            }

            // No entry call has the same or higher priority, insert at the beginnning of the list
            Insert(0, item);
        }

    }

}
